//--------------------------------------------------------------------------
// Based on original code by Microsoft:
// <copyright file="XmlSiteMapProvider.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
// Caching amends:
//		http://blogs.msdn.com/dannychen/archive/2006/04/17/577621.aspx
//--------------------------------------------------------------------------

using System;
using System.Collections;
using System.Collections.Specialized;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Configuration;
using System.Configuration.Provider;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Resources;
using System.Security;
using System.Security.Permissions;
using System.Web.Compilation;
using System.Web.Configuration;
using System.Web.Hosting;
using System.Web;
using System.Web.UI;
using System.Web.Util;
using System.Web.Caching;
using System.Xml;
using System.Xml.XPath;

using DNAide.Core;
using DNAide.Configuration;
using DNAide.IO;
using DNAide.Xml;

namespace DNAide.Web
{
	/// <summary>
	/// </summary>
	public class DNAideXmlSiteMapProvider : DNAideStaticSiteMapProvider
	{

		#region Properties
		
		/// <summary>
		/// </summary>
		public override SiteMapNode RootNode
		{
			get
			{
				BuildSiteMap();
				return ReturnNodeIfAccessible(_siteMapNode);
			}
		}

		/// <summary>
		/// </summary>
		public String SiteMapVirtualPath
		{
			get
			{
				return _normalizedVirtualPath;
			}
		}

		/// <summary>
		/// </summary>
		public String SiteMapPath
		{
			get
			{
				return HttpContext.Current.Server.MapPath(_normalizedVirtualPath);
			}
		}
		#endregion


		#region Private Attributes

		private string _fileName;
		private string _virtualPath;
		private string _normalizedVirtualPath;
		private SiteMapNode _siteMapNode;
		private XmlDocument _document;
		private bool _initialized;
		private StringCollection _parentSiteMapFileCollection;

		private const string _providerAttributeName = "provider";
		private const string _siteMapFileAttributeName = "siteMapFile";
		private const string _siteMapNodeName = "siteMapNode";
		private const string _xmlSiteMapFileExtension = ".sitemap";
		private const string _resourcePrefix = "$resources:";
		private const string _securityTrimmingEnabledAttributeName = "securityTrimmingEnabled";
		private const int _resourcePrefixLength = 10;
		private const char _resourceKeySeparator = ',';
		private static readonly char[] _seperators = new char[] { ';', ',' };

		private ArrayList _childProviderList;

		// table containing mappings from child providers to their root nodes.
		private Hashtable _childProviderTable;
		
		#endregion


		#region Private Properties

		private ArrayList ChildProviderList
		{
			get
			{
				if (_childProviderList == null)
				{
					lock (_lock)
					{
						if (_childProviderList == null)
						{
							_childProviderList = ArrayList.ReadOnly(new ArrayList(ChildProviderTable.Keys));
						}
					}
				}

				return _childProviderList;
			}
		}

		private Hashtable ChildProviderTable
		{
			get
			{
				if (_childProviderTable == null)
				{
					lock (_lock)
					{
						if (_childProviderTable == null)
						{
							_childProviderTable = new Hashtable();
						}
					}
				}

				return _childProviderTable;
			}
		}

		#endregion

		/// <summary>
		/// </summary>
		public DNAideXmlSiteMapProvider()
		{
		}

		/// <summary>
		/// </summary>
		/// <param name="node"></param>
		/// <param name="parentNode"></param>
		protected override void AddNode(SiteMapNode node, SiteMapNode parentNode)
		{
			if (node == null)
			{
				throw new ArgumentNullException("node");
			}

			if (parentNode == null)
			{
				throw new ArgumentNullException("parentNode");
			}

			SiteMapProvider ownerProvider = node.Provider;
			SiteMapProvider parentOwnerProvider = parentNode.Provider;

			if (ownerProvider != this)
			{

				throw new ArgumentException(StringAide.FormatInvariant(Resources.DNAideStaticSiteMapProvider.CannotAddNode, node.ToString()), "xmlNode");
			}

			if (parentOwnerProvider != this)
			{
				throw new ArgumentException(StringAide.FormatInvariant(Resources.DNAideStaticSiteMapProvider.CannotAddNode, parentNode.ToString()), "parentNode");
			}

			lock (_lock)
			{
				// First remove it from its current location.
				RemoveNode(node);
				AddNodeInternal(node, parentNode, null);
			}
		}



		private void AddNodeInternal(SiteMapNode node, SiteMapNode parentNode, XmlNode xmlNode)
		{
			lock (_lock)
			{
				String url = node.Url;
				String key = node.Key;

				bool isValidUrl = false;

				// Only add the xmlNode to the url table if it's a static xmlNode.
				if (!String.IsNullOrEmpty(url))
				{
					if (UrlTable[url] != null)
					{
						if (xmlNode != null)
						{
							throw new ConfigurationErrorsException(
									StringAide.FormatInvariant(Resources.DNAideStaticSiteMapProvider.MultipleNodesWithIdenticalUrl, url),
									xmlNode);
						}
						else
						{
							throw new InvalidOperationException(StringAide.FormatInvariant(
									Resources.DNAideStaticSiteMapProvider.MultipleNodesWithIdenticalUrl, url));
						}
					}

					isValidUrl = true;
				}

				if (KeyTable.Contains(key))
				{
					if (xmlNode != null)
					{
						throw new ConfigurationErrorsException(
								StringAide.FormatInvariant(Resources.DNAideStaticSiteMapProvider.MultipleNodesWithIdenticalKey, key),
								xmlNode);
					}
					else
					{
						throw new InvalidOperationException(
							 StringAide.FormatInvariant(Resources.DNAideStaticSiteMapProvider.MultipleNodesWithIdenticalKey, key));
					}
				}

				if (isValidUrl)
				{
					UrlTable[url] = node;
				}

				KeyTable[key] = node;

				// Add the new xmlNode into parentNode collection
				if (parentNode != null)
				{
					ParentNodeTable[node] = parentNode;

					if (ChildNodeCollectionTable[parentNode] == null)
					{
						ChildNodeCollectionTable[parentNode] = new SiteMapNodeCollection();
					}

					((SiteMapNodeCollection)ChildNodeCollectionTable[parentNode]).Add(node);
				}
			}
		}


		/// <summary>
		/// </summary>
		/// <param name="providerName"></param>
		/// <param name="parentNode"></param>
		protected virtual void AddProvider(String providerName, SiteMapNode parentNode)
		{
			if (parentNode == null)
			{
				throw new ArgumentNullException("parentNode");
			}

			if (parentNode.Provider != this)
			{
				throw new ArgumentException(StringAide.FormatInvariant(Resources.DNAideStaticSiteMapProvider.CannotAddNode, parentNode.ToString()), "parentNode");
			}

			SiteMapNode node = GetNodeFromProvider(providerName);
			AddNodeInternal(node, parentNode, null);
		}


		/// <summary>
		/// </summary>
		/// <returns></returns>
		public override SiteMapNode BuildSiteMap()
		{

			SiteMapNode tempNode = _siteMapNode;

			// If siteMap is already constructed, simply returns it.
			// Child providers will only be updated when the parent providers need to access them.
			if (tempNode != null)
			{
				return tempNode;
			}

			XmlDocument document = GetConfigDocument();

			lock (_lock)
			{
				if (_siteMapNode != null)
				{
					return _siteMapNode;
				}

				Clear();

				// Need to check if the sitemap file exists before opening it.
				CheckSiteMapFileExists();

				try
				{
					using (Stream stream = HostingEnvironment.VirtualPathProvider.GetFile(_normalizedVirtualPath).Open())
					{
						XmlReader reader = new XmlTextReader(stream);
						document.Load(reader);
					}
				}
				catch (Exception e)
				{
					throw new ConfigurationErrorsException(
							StringAide.FormatInvariant(Resources.DNAideStaticSiteMapProvider.ErrorLoadingConfigFile, _virtualPath, e.Message), e);
				}

				XmlNode node = null;
				foreach (XmlNode siteMapMode in document.ChildNodes)
				{
					if (String.Equals(siteMapMode.Name, "siteMap", StringComparison.Ordinal))
					{
						node = siteMapMode;
						break;
					}
				}

				if (node == null)
					throw new ConfigurationErrorsException(
							StringAide.FormatInvariant(Resources.DNAideStaticSiteMapProvider.TopElementMustBeSiteMap),
							document);

				bool enableLocalization = false;
				XmlAide.GetAndRemoveBooleanAttribute(node, "enableLocalization", false, ref enableLocalization);
				EnableLocalization = enableLocalization;

				XmlNode topElement = null;
				foreach (XmlNode subNode in node.ChildNodes)
				{
					if (subNode.NodeType == XmlNodeType.Element)
					{
						if (!_siteMapNodeName.Equals(subNode.Name))
						{
							throw new ConfigurationErrorsException(
									StringAide.FormatInvariant(Resources.DNAideStaticSiteMapProvider.OnlySiteMapNodeAllowed),
									subNode);
						}

						if (topElement != null)
						{
							throw new ConfigurationErrorsException(
									StringAide.FormatInvariant(Resources.DNAideStaticSiteMapProvider.OnlyOneSiteMapNodeRequiredAtTop),
									subNode);
						}

						topElement = subNode;
					}
				}

				if (topElement == null)
				{
					throw new ConfigurationErrorsException(
							 StringAide.FormatInvariant(Resources.DNAideStaticSiteMapProvider.OnlyOneSiteMapNodeRequiredAtTop),
							 node);
				}

				Queue queue = new Queue(50);

				// The parentnode of the top xmlNode does not exist,
				// simply add a null to satisfy the ConvertFromXmlNode condition.
				queue.Enqueue(null);
				queue.Enqueue(topElement);
				_siteMapNode = ConvertFromXmlNode(queue);

				return _siteMapNode;
			}
		}



		private void CheckSiteMapFileExists()
		{
			BuildSiteMapFilePrivate();

			if (!HostingEnvironment.VirtualPathProvider.FileExists(_normalizedVirtualPath))
			{
				throw new InvalidOperationException(
					StringAide.FormatInvariant(Resources.DNAideStaticSiteMapProvider.FileNameDoesNotExist, _virtualPath));
			}
		}


		private void BuildSiteMapFilePrivate()
		{

			if (!HostingEnvironment.VirtualPathProvider.FileExists(_normalizedVirtualPath))
			{
				lock (_lock)
				{
					if (!HostingEnvironment.VirtualPathProvider.FileExists(_normalizedVirtualPath))
					{
						BuildSiteMapFile();
					}
				}
			}


			// Try to build a site map file
			//if (m_AlwaysCallBuildSiteMapFile)
			//{
			//  lock (m_Lock)
			//  {
			//    BuildSiteMapFile();
			//  }
			//}
			//else if (!HostingEnvironment.VirtualPathProvider.FileExists(m_NormalizedVirtualPath))
			//{
			//  lock (m_Lock)
			//  {
			//    if (!HostingEnvironment.VirtualPathProvider.FileExists(m_NormalizedVirtualPath))
			//    {
			//      BuildSiteMapFile();
			//    }
			//  }
			//}
		}
		/// <summary>
		/// </summary>
		public virtual void BuildSiteMapFile()
		{
			// Allows you to create your own sitemap file from other sources
		}

		/// <summary>
		/// 
		/// </summary>
		protected override void Clear()
		{
			lock (_lock)
			{
				ChildProviderTable.Clear();
				_siteMapNode = null;
				_childProviderList = null;

				base.Clear();
			}
		}



		// helper method to convert an XmlNode to a SiteMapNode
		private SiteMapNode ConvertFromXmlNode(Queue queue)
		{

			SiteMapNode rootNode = null;
			while (true)
			{
				if (queue.Count == 0)
				{
					return rootNode;
				}

				SiteMapNode parentNode = (SiteMapNode)queue.Dequeue();
				XmlNode xmlNode = (XmlNode)queue.Dequeue();

				SiteMapNode node = null;

				if (!_siteMapNodeName.Equals(xmlNode.Name))
				{
					throw new ConfigurationErrorsException(
							StringAide.FormatInvariant(Resources.DNAideStaticSiteMapProvider.OnlySiteMapNodeAllowed),
							xmlNode);
				}

				string providerName = null;
				XmlAide.GetAndRemoveNonEmptyStringAttribute(xmlNode, _providerAttributeName, false, ref providerName);

				// If the siteMapNode references another provider
				if (providerName != null)
				{
					node = GetNodeFromProvider(providerName);

					// No other config or child nodes are allowed on a provider xmlNode.
					XmlAide.CheckForUnrecognizedAttributes(xmlNode);
					XmlAide.CheckForNonCommentChildNodes(xmlNode);
				}
				else
				{
					string siteMapFile = null;
					XmlAide.GetAndRemoveNonEmptyStringAttribute(xmlNode, _siteMapFileAttributeName, false, ref siteMapFile);

					if (siteMapFile != null)
					{
						node = GetNodeFromSiteMapFile(xmlNode, siteMapFile);
					}
					else
					{
						node = GetNodeFromXmlNode(xmlNode, queue);
					}
				}

				AddNodeInternal(node, parentNode, xmlNode);

				if (rootNode == null)
				{
					rootNode = node;
				}
			}
		}

		/// <summary>
		/// </summary>
		/// <param name="disposing"></param>
		protected virtual void Dispose(Boolean disposing)
		{
		}

		/// <summary>
		/// </summary>
		public void Dispose()
		{
			Dispose(true);
			GC.SuppressFinalize(this);
		}

		private void EnsureChildSiteMapProviderUpToDate(SiteMapProvider childProvider)
		{
			DNAideXmlSiteMapProvider childProviderCast = (DNAideXmlSiteMapProvider)childProvider;

			SiteMapNode oldNode = (SiteMapNode)ChildProviderTable[childProvider];

			// If the child provider table has been updated, simply return null.
			// This will happen when the current provider's sitemap file is changed or Clear() is called;
			if (oldNode == null)
			{
				return;
			}

			SiteMapNode newNode = childProviderCast.GetRootNodeCore();
			if (newNode == null)
			{
				throw new ProviderException(StringAide.FormatInvariant(Resources.DNAideStaticSiteMapProvider.InvalidSiteMapNodeReturned, childProvider.Name));
			}

			// child providers have been updated.
			if (!oldNode.Equals(newNode))
			{

				lock (_lock)
				{
					oldNode = (SiteMapNode)ChildProviderTable[childProvider];
					// If the child provider table has been updated, simply return null. See above.
					if (oldNode == null)
					{
						return;
					}

					newNode = childProviderCast.GetRootNodeCore();
					if (newNode == null)
					{
						throw new ProviderException(StringAide.FormatInvariant(Resources.DNAideStaticSiteMapProvider.InvalidSiteMapNodeReturned, childProvider.Name));
					}

					if (!oldNode.Equals(newNode))
					{

						// If the current provider does not contain any nodes but one child provider
						// ie. m_SiteMapNode == oldNode
						// the oldNode needs to be removed from Url table and the new xmlNode will be added.
						if (_siteMapNode.Equals(oldNode))
						{
							UrlTable.Remove(oldNode.Url);
							KeyTable.Remove(oldNode.Key);

							UrlTable.Add(newNode.Url, newNode);
							KeyTable.Add(newNode.Key, newNode);

							_siteMapNode = newNode;
						}

						// First find the parent xmlNode
						SiteMapNode parent = (SiteMapNode)ParentNodeTable[oldNode];

						// parent is null when the provider does not contain any static nodes, ie.
						// it only contains definition to include one child provider.
						if (parent != null)
						{
							// Update the child nodes table
							SiteMapNodeCollection list = (SiteMapNodeCollection)ChildNodeCollectionTable[parent];

							// Add the newNode to where the oldNode is within parent xmlNode's collection.
							int index = list.IndexOf(oldNode);
							if (index != -1)
							{
								list.Remove(oldNode);
								list.Insert(index, newNode);
							}
							else
							{
								list.Add(newNode);
							}

							// Update the parent table
							ParentNodeTable[newNode] = parent;
							ParentNodeTable.Remove(oldNode);

							// Update the Url table
							UrlTable.Remove(oldNode.Url);
							KeyTable.Remove(oldNode.Key);

							UrlTable.Add(newNode.Url, newNode);
							KeyTable.Add(newNode.Key, newNode);
						}
						else
						{
							// Notify the parent provider to update its child provider collection.
							DNAideXmlSiteMapProvider provider = ParentProvider as DNAideXmlSiteMapProvider;
							if (provider != null)
							{
								provider.EnsureChildSiteMapProviderUpToDate(this);
							}
						}

						// Update provider nodes;
						ChildProviderTable[childProvider] = newNode;
						_childProviderList = null;
					}
				}
			}
		}

	
		/// <summary>
		/// Returns sitemap xmlNode; Search recursively in child providers if not found.
		/// </summary>
		/// <param name="rawUrl"></param>
		/// <returns></returns>
		public override SiteMapNode FindSiteMapNode(String rawUrl)
		{
			SiteMapNode node = base.FindSiteMapNode(rawUrl);

			if (node == null)
			{
				foreach (SiteMapProvider provider in ChildProviderList)
				{
					// First make sure the child provider is up-to-date.
					EnsureChildSiteMapProviderUpToDate(provider);

					node = provider.FindSiteMapNode(rawUrl);
					if (node != null)
					{
						return node;
					}
				}
			}

			return node;
		}
 
		/// <summary>
		/// Returns sitemap xmlNode; Search recursively in child providers if not found.
		/// </summary>
		/// <param name="key"></param>
		/// <returns></returns>
		public override SiteMapNode FindSiteMapNodeFromKey(String key)
		{
			SiteMapNode node = base.FindSiteMapNodeFromKey(key);

			if (node == null)
			{
				foreach (SiteMapProvider provider in ChildProviderList)
				{
					// First make sure the child provider is up-to-date.
					EnsureChildSiteMapProviderUpToDate(provider);

					node = provider.FindSiteMapNodeFromKey(key);
					if (node != null)
					{
						return node;
					}
				}
			}

			return node;
		}


		private XmlDocument GetConfigDocument()
		{

			if (_document == null)
			{
				if (!_initialized)
				{
					throw new InvalidOperationException(
							StringAide.FormatInvariant(Resources.DNAideStaticSiteMapProvider.NotInitialized));
				}

				// Do the error checking here
				if (_virtualPath == null)
				{
					throw new ArgumentException(
							StringAide.FormatInvariant(Resources.DNAideStaticSiteMapProvider.MissingSiteMapFile, _siteMapFileAttributeName));
				}

				if (!Path.GetExtension(_virtualPath).Equals(_xmlSiteMapFileExtension, StringComparison.OrdinalIgnoreCase))
				{
					throw new InvalidOperationException(
							StringAide.FormatInvariant(Resources.DNAideStaticSiteMapProvider.InvalidExtension, _virtualPath));
				}

				// Ensure the appdomain virtualpath has proper trailing slash
				_normalizedVirtualPath =
						VirtualPathUtility.Combine(AppDomainAppVirtualPathWithTrailingSlash, _virtualPath);

				// Make sure the file exists
				CheckSiteMapFileExists();

				_parentSiteMapFileCollection = new StringCollection();

				DNAideXmlSiteMapProvider xmlParentProvider = ParentProvider as DNAideXmlSiteMapProvider;

				if (xmlParentProvider != null && xmlParentProvider._parentSiteMapFileCollection != null)
				{
					if (xmlParentProvider._parentSiteMapFileCollection.Contains(_normalizedVirtualPath))
					{
						throw new InvalidOperationException(
								StringAide.FormatInvariant(Resources.DNAideStaticSiteMapProvider.FileNameAlreadyInUse, _virtualPath));
					}

					// Copy the sitemapfiles in used from parent provider to current provider.
					foreach (string filename in xmlParentProvider._parentSiteMapFileCollection)
					{
						_parentSiteMapFileCollection.Add(filename);
					}
				}

				// Add current sitemap file to the collection
				_parentSiteMapFileCollection.Add(_normalizedVirtualPath);
				_fileName = HostingEnvironment.MapPath(_normalizedVirtualPath);
				_document = new XmlDocument();
			}

			if (!String.IsNullOrEmpty(_fileName))
			{
				CacheItemRemovedCallback handler = new CacheItemRemovedCallback(OnConfigFileChange);
				CacheDependency dep = new CacheDependency(_fileName);
				HttpContext.Current.Cache.Add(CastAide.AsString(GetHashCode()), "", dep,
						Cache.NoAbsoluteExpiration, Cache.NoSlidingExpiration,
						CacheItemPriority.Normal, handler);

				ResourceKey = (new FileInfo(_fileName)).Name;
			}

			return _document;

		}


		private SiteMapNode GetNodeFromProvider(String providerName)
		{
			SiteMapProvider provider = GetProviderFromName(providerName);
			SiteMapNode node = null;

			// Check infinite recursive sitemap files
			if (provider is DNAideXmlSiteMapProvider)
			{
				DNAideXmlSiteMapProvider xmlProvider = (DNAideXmlSiteMapProvider)provider;

				StringCollection parentSiteMapFileCollection = new StringCollection();
				if (_parentSiteMapFileCollection != null)
				{
					foreach (string filename in _parentSiteMapFileCollection)
					{
						parentSiteMapFileCollection.Add(filename);
					}
				}

				// Make sure the provider is initialized before adding to the collection.
				xmlProvider.BuildSiteMap();

				parentSiteMapFileCollection.Add(_normalizedVirtualPath);
				if (parentSiteMapFileCollection.Contains(xmlProvider._normalizedVirtualPath))
				{
					throw new InvalidOperationException(StringAide.FormatInvariant(Resources.DNAideStaticSiteMapProvider.FileNameAlreadyInUse, xmlProvider._virtualPath));
				}

				xmlProvider._parentSiteMapFileCollection = parentSiteMapFileCollection;
			}

			node = provider.RootNode;
			if (node == null)
			{
				throw new InvalidOperationException(
						StringAide.FormatInvariant(Resources.DNAideStaticSiteMapProvider.InvalidGetRootNodeCore, ((ProviderBase)provider).Name));
			}

			ChildProviderTable.Add(provider, node);
			_childProviderList = null;

			provider.ParentProvider = this;

			return node;
		}

		private SiteMapNode GetNodeFromSiteMapFile(XmlNode xmlNode, String siteMapFile)
		{

			SiteMapNode node = null;

			// For external sitemap files, its secuity setting is inherited from parent provider
			bool secuityTrimmingEnabled = SecurityTrimmingEnabled;
			XmlAide.GetAndRemoveBooleanAttribute(xmlNode, _securityTrimmingEnabledAttributeName, false, ref secuityTrimmingEnabled);

			// No other config or non-comment nodes are allowed on a siteMapFile xmlNode
			XmlAide.CheckForUnrecognizedAttributes(xmlNode);
			XmlAide.CheckForNonCommentChildNodes(xmlNode);

			DNAideXmlSiteMapProvider childProvider = new DNAideXmlSiteMapProvider();

			// siteMapFile was relative to the sitemap file where this xmlnode is defined, make it an application pathToCheck.
			siteMapFile = VirtualPathUtility.Combine(VirtualPathUtility.GetDirectory(_normalizedVirtualPath), siteMapFile);

			childProvider.ParentProvider = this;
			childProvider.Initialize(siteMapFile, secuityTrimmingEnabled);
			childProvider.BuildSiteMap();

			node = childProvider._siteMapNode;

			ChildProviderTable.Add(childProvider, node);
			_childProviderList = null;

			return node;
		}

		private static void HandleResourceAttribute(XmlNode xmlNode, ref NameValueCollection collection, 
			string attrName, ref string text, bool allowImplicitResource)
		{
			if (String.IsNullOrEmpty(text))
			{
				return;
			}

			string resourceKey = null;
			string temp = text.TrimStart(new char[] { ' ' });

			if (temp != null && temp.Length > _resourcePrefixLength)
			{
				if (temp.ToLower(CultureInfo.InvariantCulture).StartsWith(_resourcePrefix, StringComparison.Ordinal))
				{
					if (!allowImplicitResource)
					{
						throw new ConfigurationErrorsException(
								StringAide.FormatInvariant(Resources.DNAideStaticSiteMapProvider.MultipleResourceDefinition, attrName), xmlNode);
					}

					resourceKey = temp.Substring(_resourcePrefixLength + 1);

					if (resourceKey.Length == 0)
					{
						throw new ConfigurationErrorsException(
								StringAide.FormatInvariant(Resources.DNAideStaticSiteMapProvider.ResourceKeyCannotBeEmpty), xmlNode);
					}

					// Retrieve className from attribute
					string className = null;
					string key = null;
					int index = resourceKey.IndexOf(_resourceKeySeparator);
					if (index == -1)
					{
						throw new ConfigurationErrorsException(
								StringAide.FormatInvariant(
								Resources.DNAideStaticSiteMapProvider.InvalidResourceKey, resourceKey), xmlNode);
					}

					className = resourceKey.Substring(0, index);
					key = resourceKey.Substring(index + 1);

					// Retrieve resource key and default value from attribute
					int defaultIndex = key.IndexOf(_resourceKeySeparator);
					if (defaultIndex != -1)
					{
						text = key.Substring(defaultIndex + 1);
						key = key.Substring(0, defaultIndex);
					}
					else
					{
						text = null;
					}

					if (collection == null)
					{
						collection = new NameValueCollection();
					}

					collection.Add(attrName, className.Trim());
					collection.Add(attrName, key.Trim());
				}
			}
		}

		/// <summary>
		/// </summary>
		/// <param name="xmlNode"></param>
		/// <param name="queue"></param>
		/// <returns></returns>
		public virtual SiteMapNode GetNodeFromXmlNode(XmlNode xmlNode, Queue queue)
		{
			if (xmlNode == null)
			{
				throw new ArgumentNullException("xmlNode");
			}

			if (queue == null)
			{
				throw new ArgumentNullException("queue");
			}

			SiteMapNode node = null;
			// static nodes
			string key = null, title = null, url = null, description = null, roles = null, resourceKey = null;

			// Url attribute is NOT required for a xml xmlNode.
			XmlAide.GetAndRemoveStringAttribute(xmlNode, "key", false, ref key);
			XmlAide.GetAndRemoveStringAttribute(xmlNode, "url", false, ref url);
			XmlAide.GetAndRemoveStringAttribute(xmlNode, "title", false, ref title);
			XmlAide.GetAndRemoveStringAttribute(xmlNode, "description", false, ref description);
			XmlAide.GetAndRemoveStringAttribute(xmlNode, "roles", false, ref roles);
			XmlAide.GetAndRemoveStringAttribute(xmlNode, "resourceKey", false, ref resourceKey);

			// Do not add the resourceKey if the resource is not valid.
			if (!String.IsNullOrEmpty(resourceKey) &&
					!ValidateResource(ResourceKey, resourceKey + ".title"))
			{
				resourceKey = null;
			}

			XmlAide.CheckForbiddenAttribute(xmlNode, _securityTrimmingEnabledAttributeName);

			NameValueCollection resourceKeyCollection = null;
			bool allowImplicitResourceAttribute = String.IsNullOrEmpty(resourceKey);
			HandleResourceAttribute(xmlNode, ref resourceKeyCollection,
					"title", ref title, allowImplicitResourceAttribute);
			HandleResourceAttribute(xmlNode, ref resourceKeyCollection,
					"description", ref description, allowImplicitResourceAttribute);

			ArrayList roleList = new ArrayList();
			if (roles != null)
			{
				int foundIndex = roles.IndexOf('?');
				if (foundIndex != -1)
				{
					throw new ConfigurationErrorsException(
							StringAide.FormatInvariant(Resources.Global.AuthRuleNamesCantContainChar,
									roles[foundIndex].ToString(CultureInfo.InvariantCulture)), xmlNode);
				}

				foreach (string role in roles.Split(_seperators))
				{
					string trimmedRole = role.Trim();
					if (trimmedRole.Length > 0)
					{
						roleList.Add(trimmedRole);
					}
				}
			}
			roleList = ArrayList.ReadOnly(roleList);

			// Make urls absolute.
			if (!String.IsNullOrEmpty(url))
			{
				// URL needs to be trimmed.
				url = url.Trim();

				if (!PathAide.IsAbsolutePhysicalPath(url))
				{
					if (UrlAide.IsRelativeUrl(url))
					{
						string virtualPath = url;

						int qs = url.IndexOf('?');
						if (qs != -1)
						{
							virtualPath = url.Substring(0, qs);
						}

						// Make sure the pathToCheck is adjusted properly
						virtualPath =
								VirtualPathUtility.Combine(AppDomainAppVirtualPathWithTrailingSlash, virtualPath);

						// Make it an absolute virtualPath
						virtualPath = VirtualPathUtility.ToAbsolute(virtualPath);

						if (qs != -1)
						{
							virtualPath += url.Substring(qs);
						}

						url = virtualPath;
					}
				}

				// Reject any suspicious or mal-formed Urls.
				string decodedUrl = HttpUtility.UrlDecode(url);
				if (!String.Equals(url, decodedUrl, StringComparison.Ordinal))
				{
					throw new ConfigurationErrorsException(
							StringAide.FormatInvariant(Resources.Global.PropertyHadMalformedUrl, "url", url), xmlNode);
				}
			}
		

			// Set key
			if (String.IsNullOrEmpty(key))
			{
				if (!String.IsNullOrEmpty(url))
				{
					key = url.ToLowerInvariant();
				}
				else
				{
					key = Guid.NewGuid().ToString();
				}
			}

			// attribute collection does not contain pre-defined properties like title, url, etc.
			ConfigAide.ReadOnlyNameValueCollection attributeCollection = new ConfigAide.ReadOnlyNameValueCollection();
			attributeCollection.SetReadOnly(false);
			foreach (XmlAttribute attribute in xmlNode.Attributes)
			{
				string value = attribute.Value;
				HandleResourceAttribute(xmlNode, ref resourceKeyCollection, attribute.Name, ref value, allowImplicitResourceAttribute);
				attributeCollection[attribute.Name] = value;
			}
			attributeCollection.SetReadOnly(true);

			node = new SiteMapNode(this, key, url, title, description, roleList, attributeCollection, resourceKeyCollection, resourceKey);
			node.ReadOnly = true;

			foreach (XmlNode subNode in xmlNode.ChildNodes)
			{
				if (subNode.NodeType != XmlNodeType.Element)
					continue;

				queue.Enqueue(node);
				queue.Enqueue(subNode);
			}

			return node;
		}

		private static SiteMapProvider GetProviderFromName(String providerName)
		{
			Debug.Assert(providerName != null);

			SiteMapProvider provider = SiteMap.Providers[providerName];
			if (provider == null)
			{
				throw new ProviderException(StringAide.FormatInvariant(Resources.Global.ProviderNotFound, providerName));
			}

			return provider;
		}

		/// <summary>
		/// </summary>
		/// <returns></returns>
		protected override SiteMapNode GetRootNodeCore()
		{
			BuildSiteMap();
			return _siteMapNode;
		}


		/// <summary>
		/// </summary>
		/// <param name="name"></param>
		/// <param name="config"></param>
		public override void Initialize(String name, NameValueCollection config)
		{
			if (_initialized)
			{
				throw new InvalidOperationException(
						StringAide.FormatInvariant(Resources.DNAideStaticSiteMapProvider.CannotBeInitedTwice));
			}

			if (config != null)
			{
				if (string.IsNullOrEmpty(config["description"]))
				{
					config.Remove("description");
					config.Add("description", StringAide.FormatInvariant(Resources.DNAideStaticSiteMapProvider.Description));
				}

				string siteMapFile = null;
				ConfigAide.GetAndRemoveStringAttribute(config, _siteMapFileAttributeName, ref siteMapFile);
				_virtualPath = siteMapFile;
			}

			base.Initialize(name, config);

			if (config != null)
			{
				ConfigAide.CheckForUnrecognizedAttributes(config);
			}

			_initialized = true;
		}

		private void Initialize(String virtualPath, Boolean secuityTrimmingEnabled)
		{
			NameValueCollection coll = new NameValueCollection();
			coll.Add(_siteMapFileAttributeName, virtualPath);
			coll.Add(_securityTrimmingEnabledAttributeName, StringAide.GetStringFromBool(secuityTrimmingEnabled));

			// Use the siteMapFile virtual pathToCheck as the provider name
			Initialize(virtualPath, coll);
		}


		private void OnConfigFileChange(String key, Object value, CacheItemRemovedReason reason)
		{
			// Notifiy the parent for the change.
			DNAideXmlSiteMapProvider parentProvider = ParentProvider as DNAideXmlSiteMapProvider;
			if (parentProvider != null)
			{
				parentProvider.OnConfigFileChange(key, value, reason);
			}
			Clear();
		} 

		/// <summary>
		/// </summary>
		/// <param name="node"></param>
		protected override void RemoveNode(SiteMapNode node)
		{
			if (node == null)
			{
				throw new ArgumentNullException("node");
			}

			SiteMapProvider ownerProvider = node.Provider;

			if (ownerProvider != this)
			{
				// Only nodes defined in this provider tree can be removed.
				SiteMapProvider parentProvider = ownerProvider.ParentProvider;
				while (parentProvider != this)
				{
					if (parentProvider == null)
					{
						// Cannot remove nodes defined in other providers
						throw new InvalidOperationException(StringAide.FormatInvariant(
								Resources.DNAideStaticSiteMapProvider.CannotRemoveNode, node.ToString(),
								this.Name, ownerProvider.Name));
					}

					parentProvider = parentProvider.ParentProvider;
				}
			}

			DNAideXmlSiteMapProvider ownerProviderCast = (DNAideXmlSiteMapProvider)ownerProvider;
			if (node.Equals(ownerProviderCast.GetRootNodeCore()))
			{
				throw new InvalidOperationException(StringAide.FormatInvariant(Resources.DNAideStaticSiteMapProvider.CannotRemoveNode));
			}

			if (ownerProvider != this)
			{
				// Remove xmlNode from the owner provider.
				ownerProviderCast.RemoveNode(node);
			}

			base.RemoveNode(node);
		}

		/// <summary>
		/// </summary>
		/// <param name="providerName"></param>
		protected virtual void RemoveProvider(String providerName)
		{
			if (providerName == null)
			{
				throw new ArgumentNullException("providerName");
			}

			lock (_lock)
			{
				SiteMapProvider provider = GetProviderFromName(providerName);
				SiteMapNode rootNode = (SiteMapNode)ChildProviderTable[provider];

				if (rootNode == null)
				{
					throw new InvalidOperationException(StringAide.FormatInvariant(Resources.DNAideStaticSiteMapProvider.CannotFindProvider, provider.Name, this.Name));
				}

				provider.ParentProvider = null;
				ChildProviderTable.Remove(provider);
				_childProviderList = null;

				base.RemoveNode(rootNode);
			}
		}

		// Note that this only returns false if the classKey cannot be found, regardless of resourceKey.
		private static bool ValidateResource(string classKey, string resourceKey)
		{
			try
			{
				HttpContext.GetGlobalResourceObject(classKey, resourceKey, CultureInfo.InvariantCulture);
			}
			catch (MissingManifestResourceException)
			{
				return false;
			}

			return true;
		}



		
	}
}
